﻿#include "pch.h"
#include "Game.h"
#include "Resource.h"

/**
 * 构造方法。
 * @param callback  回调
 * @param drawer    绘图者
 * @param gameBoard 游戏区域
 */
Game::Game(CWnd* callback, CDC* drawer, CWnd* gameBoard) :
    mCallback(callback),
    mDifficulty(EASY),
    mDrawer(drawer),
    mGameBoard(gameBoard),
    mPicHasBorder(TRUE),
    mStatus(END) {
    // 初始化成员
    mImage.CreateCompatibleBitmap(drawer, Const::GAME_BOARD_WIDTH, Const::GAME_BOARD_HEIGHT);
    mImageDC.CreateCompatibleDC(drawer);
    mImageDC.SelectObject(&mImage);
    memset(mPictures, 0, sizeof(mPictures));

    // 设置图片
    CImage bitmap;
    bitmap.LoadFromResource(AfxGetInstanceHandle(), IDB_PIC);
    this->SetPicture(&bitmap);

    // 置随机数种子
    srand((UINT)time(NULL));
}

/**
 * 析构方法。
 */
Game::~Game() {
    this->DeletePictures();
}

/**
 * 创建所有图片控件。
 */
void Game::CreatePictures() {
    int left, top;                                  // 图片控件位置
    int baseCount = (int)sqrt((double)mDifficulty); // 一行（列）的图片控件数
    int size = Const::EMPTY_AREA_WIDTH / baseCount; // 图片控件尺寸
    CRect rect;                                     // 图片控件位置、尺寸
    int j = 0;                                      // 换行标志，0为第一行，1为第二行，依次类推

    // 删除所有图片
    this->DeletePictures();

    // 创建所有图片
    for (int i = 0; i < mDifficulty; i++) {
        if (i % baseCount == 0 && i != 0) { // 如果一行满了，换到下一列
            j++;
        }

        // 计算左上角坐标
        left = Const::EMPTY_AREA_X + size * (i % baseCount);
        top = Const::EMPTY_AREA_Y + size * j;

        // 设置位置、尺寸
        rect.SetRect(left, top, left + size, top + size);

        // 创建图片控件
        mPictures[i] = new PictureCtrl(mPicHasBorder, IDC_PICTURE_CTRL + i, &mImageDC, mGameBoard, &rect);
    }
}

/**
 * 删除所有图片控件。
 */
void Game::DeletePictures() {
    for (int i = 0; i < Const::MAX_PICTURE_CTRL_COUNT; i++) {
        if (mPictures[i] == NULL) {
            break;
        }
        delete mPictures[i];
        mPictures[i] = NULL;
    }
}

/**
 * 绘制所有图片控件。
 */
void Game::DrawPictures() {
    // 通知所有图片控件重绘
    for (int i = 0; i < mDifficulty; i++) {
        mPictures[i]->Invalidate(FALSE);
    }
}

/**
 * 游戏结束。
 */
void Game::GameOver() {
    // 将状态设置为已结束
    mStatus = END;

    // 发送通知
    mCallback->SendMessage(Const::UM_GAME_OVER);
}

/**
 * 获取游戏难度。
 * @return 游戏难度
 */
Game::Difficulty Game::GetDifficulty() {
    return mDifficulty;
}

/**
 * 获取游戏状态。
 * @return 游戏状态
 */
Game::Status Game::GetStatus() {
    return mStatus;
}

/**
 * 判断是否胜利。
 * @return 胜利则返回 TRUE，否则返回 FALSE
 */
BOOL Game::IsWon() {
    BOOL result = FALSE;
    for (int i = 0; i < mDifficulty; i++) {
        result = mPictures[i]->IsOnOriginalPosition();
        if (!result) {
            break;
        }
    }
    return result;
}

/**
 * 移动图片控件。
 * @param id    图片控件的 id
 */
void Game::MovePicture(UINT id) {
    if (mStatus != PLAYING) {
        return;
    }

    CRect rect;
    mPictures[id - IDC_PICTURE_CTRL]->GetWindowRect(rect);
    mPictures[id - IDC_PICTURE_CTRL]->GetParent()->ScreenToClient(rect);

    // 如果要移动的图片控件与空点相邻，则移动
    if (rect.top == mEmptyPoint.y && abs(rect.left - mEmptyPoint.x) == rect.Width() ||
        rect.left == mEmptyPoint.x && abs(rect.top - mEmptyPoint.y) == rect.Height()) {
        mPictures[id - IDC_PICTURE_CTRL]->SetWindowPos(&CWnd::wndTop,
            mEmptyPoint.x, mEmptyPoint.y, 0, 0, SWP_NOSIZE);
        mEmptyPoint.x = rect.left;
        mEmptyPoint.y = rect.top;

        // 通知所有图片控件重绘
        this->DrawPictures();

        // 判断是否胜利
        if (this->IsWon()) {
            this->GameOver();
        }
    }
}

/**
 * 获取图片控件是否有边框标志。
 * @return 有边框则为 TRUE，否则为 FALSE
 */
BOOL Game::PictureHasBorder() {
    return mPicHasBorder;
}

/**
 * 设置游戏难度。
 * @param diff  游戏难度
 */
void Game::SetDifficulty(Game::Difficulty diff) {
    // 设置难度
    mDifficulty = diff;

    // 重新创建所有图片
    this->CreatePictures();

    // 将状态设置为已结束
    mStatus = END;
}

/**
 * 设置图片。
 * @param image 图片
 */
void Game::SetPicture(CImage* image) {
    // 将图片画到游戏区域上
    CDC* dc = CDC::FromHandle(image->GetDC());
    mImageDC.StretchBlt(0, 0, Const::GAME_BOARD_WIDTH, Const::GAME_BOARD_HEIGHT, dc, 0, 0, image->GetWidth(), image->GetHeight(), SRCCOPY);
    mDrawer->BitBlt(0, 0, Const::GAME_BOARD_WIDTH, Const::GAME_BOARD_HEIGHT, &mImageDC, 0, 0, SRCCOPY);
    image->ReleaseDC();

    // 画中间的空白区域
    mDrawer->FillSolidRect(Const::EMPTY_AREA_X, Const::EMPTY_AREA_Y, Const::EMPTY_AREA_WIDTH, Const::EMPTY_AREA_HEIGHT, Const::EMPTY_AREA_COLOR);

    // 通知游戏区域重绘
    mGameBoard->Invalidate(FALSE);

    // 创建所有图片
    this->CreatePictures();

    // 将状态设置为已结束
    mStatus = END;
}

/**
 * 设置图片控件是否有边框。
 * @param hasBorder 有边框则为 TRUE，否则为 FALSE
 */
void Game::SetPictureBorder(BOOL hasBorder) {
    mPicHasBorder = hasBorder;

    // 设置所有图片控件的边框
    for (int i = 0; i < mDifficulty; i++) {
        mPictures[i]->SetBorder(hasBorder);
    }
}

/**
 * 游戏开始。
 */
void Game::Start() {
    // 重新创建所有图片
    if (mStatus != END) {
        this->CreatePictures();
    }

    // 重置成员变量
    int baseCount = (int)sqrt((double)mDifficulty);
    CRect rect;
    mPictures[0]->GetWindowRect(rect);
    mEmptyPoint.x = Const::EMPTY_AREA_X + rect.Width() * baseCount;
    mEmptyPoint.y = Const::EMPTY_AREA_Y + rect.Height() * (baseCount - 1);
    mStatus = PLAYING;

    // 右下角图片控件右移
    CPoint position;
    mPictures[mDifficulty - 1]->GetPosition(&position);
    mPictures[mDifficulty - 1]->SetWindowPos(&CWnd::wndTop, mEmptyPoint.x, mEmptyPoint.y, 0, 0, SWP_NOSIZE);
    mEmptyPoint = position;

    // 乱序其它图片控件的位置
    int index1, index2;
    for (int i = 0; i < mDifficulty - 1; i++) {
        do {
            index1 = rand() % (mDifficulty - 1);
            index2 = rand() % (mDifficulty - 1);
        } while (index1 == index2);
        this->SwapPictures(index1, index2);
    }

    // 绘制所有图片控件
    this->DrawPictures();
}

/**
 * 交换两个图片控件的位置。
 * @param index1    图片控件1的索引
 * @param index2    图片控件2的索引
 */
void Game::SwapPictures(int index1, int index2) {
    CPoint position1, position2;
    mPictures[index1]->GetPosition(&position1);
    mPictures[index2]->GetPosition(&position2);
    mPictures[index1]->SetWindowPos(&CWnd::wndTop, position2.x, position2.y, 0, 0, SWP_NOSIZE);
    mPictures[index2]->SetWindowPos(&CWnd::wndTop, position1.x, position1.y, 0, 0, SWP_NOSIZE);
}
